//
//  ConnectionsIdentifier.m
//  SolarSeek
//
//  Created by Erick Fejta on 05/02/17.
//  Copyright 2005 Erick Fejta. All rights reserved.
//

#import "ConnectionsIdentifier.h"
#import "EngineDefaults.h"

#import "Connections.h"
#import "NetSocket.h"
#import "Message.h"
#import "EnumTypes.h"

@implementation ConnectionsIdentifier

- (id)init {
	if(self = [super init]) {
		_socketListening=nil;
		_connectionOpened=nil;
		_connectionsOpening = [[NSMutableSet alloc] initWithCapacity:200];
	}
	return self;
}

- (void)dealloc {
	[_connectionOpened release];
	[_connectionsOpening release];
	[_socketListening release];
	[super dealloc];
}

- (void)stopObservingUnidentifiedConnection:(UnidentifiedConnection *)uc {
	[uc removeObserver:self forKeyPath:@"messageReceived"];
	[_connectionsOpening removeObject:uc];	//release ownership.
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(stopObservingUnidentifiedConnection:) object:uc];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
	NSAssert2([object isKindOfClass:[UnidentifiedConnection class]] && [keyPath isEqual:@"messageReceived"],@"Cannot respond to observation of keyPath:%@ of object:%@.",keyPath,object);

	//we read the first message.  So find out information about it and change the value for self->connectionOpened.
	//decide if it is a Transfer, Peer, or PiercingFirewall connection!
	
	UnidentifiedConnection *uc = object;

//NSLog(@"CI: observing message from %@:%d.",[uc addressHost],[uc portHost]);

	Message *m = [uc messageReceived];
	[m rewind];
	char c = [m getByte];
	switch(c) {
	case PeerInit:
		{
			NSString *username = [m getString],
					 *type = [m getString];
//			unsigned whatever = [m getInt],	//fejta: TODO: low: decide if we ever need these two pieces of info.
//					 token = [m getInt];
			[uc setupAsConnectionToName:username peerConnection:[type isEqual:TypeConnectionPeer]];
		}
		break;
	case PierceFireWall:
		{
			unsigned token = [m getInt];
			[uc setupAsPierceFirewallWithToken:token];
		}
		break;
	default:
		[NSException raise:@"InvalidCodeException" format:@"%@ sent an invalid code: %d.",[uc addressHost],c];
	}

	[self willChangeValueForKey:@"connectionOpened"];
	[_connectionOpened release];
	_connectionOpened = [uc retain];
	[self didChangeValueForKey:@"connectionOpened"];
	[self stopObservingUnidentifiedConnection:uc];	//this function drops ownership and releases uc.
}

#pragma mark KVC/O methods

- (BOOL)listening { return _socketListening != nil && [_socketListening isConnected]; }
- (UInt16)portListening { return [_socketListening localPort]; }
- (UnidentifiedConnection *)connectionOpened { return _connectionOpened; }


- (BOOL)shouldRequestIncomingConnectionSecond { return [[[NSUserDefaults standardUserDefaults] valueForKey:SolarSeekDefaultShouldRequestConnections] intValue] == 2; }
- (BOOL)shouldRequestIncomingConnectionFirst { 
	id o = [[NSUserDefaults standardUserDefaults] valueForKey:SolarSeekDefaultShouldRequestConnections];
	return [o intValue] == 1; 
}

#pragma mark actions

- (BOOL)startListening {
	int start = [[NSUserDefaults standardUserDefaults] integerForKey:SolarSeekDefaultPortStart];
	int end = [[NSUserDefaults standardUserDefaults] integerForKey:SolarSeekDefaultPortEnd];
	if(start > end) {
		int i = start;
		start = end;
		end = i;
		[[NSUserDefaults standardUserDefaults] setInteger:start forKey:SolarSeekDefaultPortStart];
		[[NSUserDefaults standardUserDefaults] setInteger:end forKey:SolarSeekDefaultPortEnd];
	}
	return [self startListeningOnPorts:NSMakeRange(start,end-start+1)];
}

- (BOOL)startListeningOnPorts:(NSRange)ports {
		[self willChangeValueForKey:@"portListening"];
		[self willChangeValueForKey:@"listening"];
		unsigned p = ports.location;
		[self stopListening];
		while(_socketListening==nil && p<ports.location+ports.length) {
			_socketListening = [NetSocket netsocketListeningOnPort:p];
			p++;
		}
		
		if(_socketListening) {
			[_socketListening retain];
			[_socketListening setDelegate:self];
		}
		
		[self didChangeValueForKey:@"listening"];
		[self didChangeValueForKey:@"portListening"];
		return _socketListening!=nil;
}

- (void)stopListening {
	//close and release the listening socket.
	//update "listening" value if we were listening before.
	if(_socketListening && [_socketListening isConnected]) {
		[self willChangeValueForKey:@"listening"];
		[_socketListening close];
		[_socketListening setDelegate:nil];
		[_socketListening release];
		_socketListening = nil;
		[self didChangeValueForKey:@"listening"];
	}
	else {
		[_socketListening setDelegate:nil];
		[_socketListening release];
		_socketListening = nil;
	}
}

#pragma mark Socket delegate methods

- (void)netsocket:(NetSocket *)sock connectionAccepted:(NetSocket *)newSocket {
	NSAssert1(sock == _socketListening,@"ConnectionsIdentifier is not the delegate for this socket: %@.",sock);
	

//NSLog(@"CONNECTION: Incoming connection from %@:%d.",[newSocket remoteHost],[newSocket remotePort]);
	//we accepted a socket, so read one message (the peer init or pierce firewall init message)
	//cancel and release this connection if it hasn't identified itself after 15 seconds.
	
	//normally, messageReceived will be observed next.
	
	UnidentifiedConnection *uc = [[UnidentifiedConnection alloc] initWithSocket:newSocket];
	[uc addObserver:self forKeyPath:@"messageReceived" options:nil context:nil];
	[_connectionsOpening addObject:uc];	//this object has ownership.
	[uc release];
	[uc readNextMessage];
			//this will drop ownership in 15 seconds.
	[self performSelector:@selector(stopObservingUnidentifiedConnection:) withObject:uc afterDelay:15.0];
	[newSocket open];
	[newSocket scheduleOnCurrentRunLoop];
}


@end
